package org.scisaga.tetra.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.matheclipse.basic.Config;
import org.matheclipse.core.expression.F;

public class ProcImpl extends Thread {
	
	protected String id;
	protected String name;
	protected String graph_id;
	
	// The registered Entities in this ComputeNode
	private ConcurrentHashMap<String,EntiImpl> entityRef;
	
	// In going Entities
	ArrayList<EntiImpl> entityIn;
	// Guard Function
	String cond;
	// Guard Impl
	private GuardImpl guard;
	// Out going Entities
	private ArrayList<EntiImpl> entityOut;
	// Output variable list
	private ArrayList<String> varOut;
	// Evaluation list
	private ArrayList<String> evalList;
	
	// Mathematical Script Evaluator
	private EvalJ evaluator;
	
	// A token pool to hold the generated tokens from evaluator
	private HashMap<EntiImpl,HashMap<String,String>> tokenPool = new HashMap<EntiImpl,HashMap<String,String>>();
	
	// Control use
	private volatile boolean done = false;
	
	public String status = "";
	
	// Indicate if this Process is initialized successfully
	private boolean initSuccess = true;
	
	public static Messager mgr;
	
	public ProcImpl(JSONObject p, ConcurrentHashMap<String,EntiImpl> eRef){
		
		evalList = new ArrayList<String>();
		
		entityIn = new ArrayList<EntiImpl>();
		entityOut = new ArrayList<EntiImpl>();
		varOut = new ArrayList<String>();

		try {
			// Load JSON
			
			id = p.getString("id");
			name = p.getString("name");
			this.setName("ProcImpl: "+name);
			graph_id = p.getString("graph_id");
			cond = p.getString("guard").replaceAll("\\$", "");
			entityRef = eRef;
			
			synchronized(entityRef){
			
				JSONArray entityIn = p.getJSONArray("entityIn");
				for (int i = 0; i < entityIn.length(); i++){
					String eid = entityIn.getString(i);
					if(entityRef.containsKey(eid)){
						//log(entityRef.get(entityIn.getString(i)).DBUrl);
						this.entityIn.add(entityRef.get(eid));
					} else {
						log("Entity not found : " + entityIn.getString(i));
						initSuccess = false;
					}
				}
				
				JSONArray entityOut = p.getJSONArray("entityOut");
				
				for (int i = 0; i < entityOut.length(); i++){
					String eid = entityOut.getString(i);
					if(entityRef.containsKey(eid)){
						EntiImpl e = entityRef.get(eid);
						
						this.entityOut.add(e);
						varOut.addAll(e.varList.keySet());
						tokenPool.put(e, new HashMap<String,String>());
					} else {
						log("Entity not found : " + entityOut.getString(i));
						initSuccess = false;
					}
				}
				
				if(entityIn.length() == 0 || entityOut.length()==0){
					log("Ingoing or outgoing entity may not empty");
					initSuccess = false;
				}
			}
			
			JSONArray evalArray = p.getJSONArray("eval");
			for (int i = 0; i < evalArray.length(); i++)
				evalList.add(evalArray.getString(i));
			
			evaluator = new EvalJ(evalList);
			//System.out.println(evaluator.toString());
			guard = new GuardImpl(this);
			
			
		} catch (JSONException e) {
			e.printStackTrace();
			log("Process() -> Input JSON String is invalid");
			initSuccess = false;
		}
	}
	
	/**
	 * Execute the same process repeatedly
	 */
	public void run(){
		//String out = this.name + ":";
		
		while(!done){
			
			// Get input tokens
			boolean gotten = false;
			HashMap<String,String> sToken = null;
			int i = 0;
			
			while(!gotten && !done){
				i++;
				sToken = guard.getTokens();
				if(sToken!=null && !sToken.isEmpty()) {
					status = "Running";
					//for(String key:sToken.keySet()) out += sToken.get(key) + ",";
					//out += "|";
					
					gotten = true;
					
					//System.out.println("**************"+this.name);
					//System.out.println("Before evaluate");
					//for(String key: sToken.keySet()){
					//	System.out.print("["+key+"->"+sToken.get(key)+"]");
					//}
					//System.out.println();
					
					// Suppose now token is collected successfully
					evaluator.load(sToken);
					// Now we can get the results from evaluator
					sToken = evaluator.genHash(varOut);
					
					//System.out.println("After evaluate");
					//for(String key: sToken.keySet()){
					//	System.out.print("["+key+"->"+sToken.get(key)+"]");
					//}
					//System.out.println();
					
					for(String s : sToken.keySet()){
						for(EntiImpl e : entityOut){
							if(e.varList.containsKey(s)){
								tokenPool.get(e).put(s, sToken.get(s));
							}
						}
					}
					
					// Save the token to DB
					for(EntiImpl e : entityOut){
						e.putToken(tokenPool.get(e));
					}
				} else {
					status = "Timming";
					try {
						// Control the computation delay time
						if(i <= 5){
							sleep(100);
						} else if(i <= 10){
							sleep(500);
						} else {
							sleep(1000);
						} 
					} catch (InterruptedException e) {
						log("*ERROR* Thread can't sleep [Goto Timming Status]");
						// e.printStackTrace();
					}
				}
			}
		}
		
		//System.out.println(out);
	}
	
	private static void log(String s){
		if(mgr != null){
			mgr.computeNode.log("Process :: " + s);
		} else System.out.println("Process :: " + s);
	}
	
	@SuppressWarnings("unused")
	private static void log(HashMap<String,String> token){
		if(token.isEmpty()) log("Token is EMPTY");
		String output = "";
		for(String s : token.keySet()){
			output += "["+s+","+token.get(s)+"]";
		}
		log(output);
	}
	
	public boolean initSuccess(){
		if(this.initSuccess){
			return true;
		} else return false;
	}
	
	public String get_Id(){
		return this.id;
	}
	
	// Stop the thread
	public void setDone(){
		done = true;
	}
	
	public static void main(String[] argv) throws InterruptedException{
		log("A simple Process test");

		F.initSymbols(null);
		Config.SERVER_MODE = true;
		
		String s11 = "{\"id\":\"testEntity_20100405_0\",";
		s11 += "\"name\":\"testEntity_0\",";
		s11 += "\"graph_id\":\"testNet\",";
		s11 += "\"DBUrl\":\"tcp://localhost:12358/\",";
		s11 += "\"username\":\"\",";
		s11 += "\"passwd\":\"\",";
		s11 += "\"varList\":{\"$x1\":\"INT\",\"$x2\":\"INT\"}}";
		
		String s12 = "{\"id\":\"testEntity_20100405_1\",";
		s12 += "\"name\":\"testEntity_1\",";
		s12 += "\"graph_id\":\"testNet\",";
		s12 += "\"DBUrl\":\"tcp://localhost:12358/\",";
		s12 += "\"username\":\"\",";
		s12 += "\"passwd\":\"\",";
		s12 += "\"varList\":{\"$y1\":\"INT\",\"$y2\":\"INT\"}}";
		
		String s13 = "{\"id\":\"testEntity_20100405_4\",";
		s13 += "\"name\":\"testEntity_4\",";
		s13 += "\"graph_id\":\"testNet\",";
		s13 += "\"DBUrl\":\"tcp://localhost:12358/\",";
		s13 += "\"username\":\"\",";
		s13 += "\"passwd\":\"\",";
		s13 += "\"varList\":{\"$m\":\"INT\"}}";
		
		String s21 = "{\"id\":\"testEntity_20100405_2\",";
		s21 += "\"name\":\"testEntity_2\",";
		s21 += "\"graph_id\":\"testNet\",";
		s21 += "\"DBUrl\":\"tcp://localhost:12358/\",";
		s21 += "\"username\":\"\",";
		s21 += "\"passwd\":\"\",";
		s21 += "\"varList\":{\"$z\":\"VARCHAR\"}}";
		
		String s22 = "{\"id\":\"testEntity_20100405_3\",";
		s22 += "\"name\":\"testEntity_3\",";
		s22 += "\"graph_id\":\"testNet\",";
		s22 += "\"DBUrl\":\"tcp://localhost:12358/\",";
		s22 += "\"username\":\"\",";
		s22 += "\"passwd\":\"\",";
		s22 += "\"varList\":{\"$u\":\"INT\"}}";	
		
		String s3 = "{\"id\":\"testProcess_20100405_0\",";
		s3 += "\"name\":\"testProcess_0\",";
		s3 += "\"graph_id\":\"testNet\",";
		s3 += "\"guard\":\"$x1=$y1+1 AND $x1=$m\",";
		s3 += "\"entityIn\":[\"testEntity_20100405_0\",\"testEntity_20100405_1\",\"testEntity_20100405_4\"],";
		s3 += "\"entityOut\":[\"testEntity_20100405_2\",\"testEntity_20100405_3\"],";
		s3 += "\"eval\":[\"$z=$x1+$y1\",\"$u=$x2+$y2+$m\"]}";
		
		String s4 = "{\"id\":\"testProcess_20100405_1\",";
		s4 += "\"name\":\"testProcess_1\",";
		s4 += "\"graph_id\":\"testNet\",";
		s4 += "\"guard\":\"$x1=$y1+1 AND $x1=$m\",";
		s4 += "\"entityIn\":[\"testEntity_20100405_0\",\"testEntity_20100405_1\",\"testEntity_20100405_4\"],";
		s4 += "\"entityOut\":[\"testEntity_20100405_2\",\"testEntity_20100405_3\"],";
		s4 += "\"eval\":[\"$z=$x1+$y1\",\"$u=$x2+$y2+$m\"]}";

		try {
			
			// Initialize two Entities
			JSONObject j11 = new JSONObject(s11);
			EntiImpl e11 = new EntiImpl(j11);
			if(! e11.tryDB()) log("Database Connection Error : " + e11.DBUrl);
			if(! e11.reg()) log("Can't create table : " + e11.name + "@" + e11.DBUrl);
			
			JSONObject j12 = new JSONObject(s12);
			EntiImpl e12 = new EntiImpl(j12);
			if(! e12.tryDB()) log("Database Connection Error : " + e12.DBUrl);
			if(! e12.reg()) log("Can't create table : " + e12.name + "@" + e12.DBUrl);
			
			JSONObject j13 = new JSONObject(s13);
			EntiImpl e13 = new EntiImpl(j13);
			if(! e13.tryDB()) log("Database Connection Error : " + e13.DBUrl);
			if(! e13.reg()) log("Can't create table : " + e13.name + "@" + e13.DBUrl);
			
			JSONObject j21 = new JSONObject(s21);
			EntiImpl e21 = new EntiImpl(j21);
			if(! e21.tryDB()) log("Database Connection Error : " + e21.DBUrl);
			if(! e21.reg()) log("Can't create table : " + e21.name + "@" + e21.DBUrl);
			
			JSONObject j22 = new JSONObject(s22);
			EntiImpl e22 = new EntiImpl(j22);
			if(! e22.tryDB()) log("Database Connection Error : " + e22.DBUrl);
			if(! e22.reg()) log("Can't create table : " + e22.name + "@" + e21.DBUrl);
			
			System.out.println("...Initialize done");
			
			// Fill in the testing tokens
			for(int i=0; i<10; i++){
				HashMap<String,String> token = new HashMap<String,String>();
				token.put("$x1", String.valueOf(i));
				token.put("$x2", String.valueOf(i+1));
				e11.putToken(token);
				
				token = new HashMap<String,String>();
				token.put("$y1", String.valueOf(i));
				token.put("$y2", String.valueOf(i+1));
				e12.putToken(token);
				
				token = new HashMap<String,String>();
				token.put("$m", String.valueOf(i));
				e13.putToken(token);
			}
			
			System.out.println("...Put tokens done");
			
			// Construct the entityRef table
			ConcurrentHashMap<String, EntiImpl> ref = new ConcurrentHashMap<String, EntiImpl>();
			ref.put("testEntity_20100405_0", e11);
			ref.put("testEntity_20100405_1", e12);
			ref.put("testEntity_20100405_4", e13);
			ref.put("testEntity_20100405_2", e21);
			ref.put("testEntity_20100405_3", e22);
			
			//log(ref.containsKey("testEntity_20100405_0")?"a":"b");
			//
			JSONObject j3 = new JSONObject(s3);
			ProcImpl p1 = new ProcImpl(j3, ref);
			
			JSONObject j4 = new JSONObject(s4);
			ProcImpl p2 = new ProcImpl(j4, ref);
			
			System.out.println("...Ready to fire the Process");
			if(p1.initSuccess) p1.start();
			// TODO: 单线程和多线程的区别
			if(p2.initSuccess) p2.start();
			
			Thread.sleep(6000);
			p1.setDone();
			p2.setDone();
			
			System.out.println("E11: "+e11.getTokenNum());
			System.out.println("E12: "+e12.getTokenNum());
			System.out.println("E13: "+e13.getTokenNum());
			System.out.println("E21: "+e21.getTokenNum());
			System.out.println("E22: "+e22.getTokenNum());
			
			JSONArray result1 = new JSONArray(e21.getAllTokens().toString());
			JSONArray result2 = new JSONArray(e22.getAllTokens().toString());
			String out1="";
			String out2="";
			
			for(int i = 0;i<result1.length();i++){
				JSONObject r1 = result1.getJSONObject(i);
				out1+= r1.getString("$z")+",";
			}
			
			for(int i = 0;i<result2.length();i++){
				JSONObject r2 = result2.getJSONObject(i);
				out2+= r2.getString("$u")+",";
			}
			System.out.println(out1);
			System.out.println(out2);
			
			System.out.println("...Unload the Processes and Entities");
			
			Thread.sleep(2000);
			
			e11.close();
			e12.close();
			e13.close();
			e21.close();
			e22.close();
			
			System.out.println("...Done");
		} catch (JSONException e) {
			e.printStackTrace();
			log("Simple Process testing failed");
		}
	}
}